package AnaliseLexicaFinal.Analise;

import AnaliseLexicaFinal.EstruturaDeDados.Caractere;
import AnaliseLexicaFinal.EstruturaDeDados.Codigo;
import AnaliseLexicaFinal.EstruturaDeDados.LinkToken;

/**
 *Aut&ocircmato para reconhecer os tokens
 *@version 1.0
 * @author Linton - implementacao
 * @author Elisson - Comentarios e revisao
 */
public class Automato {
	private Codigo codigo; //Codigo a ser analisado
	
	public Automato(Codigo codigo){
		this.codigo = codigo;
	}
	
	/*
	 * Retorna o proximo token reconhecido, sobre o codigo
	 */
    public LinkToken proximoToken() throws ErroLexicoException{
    	Caractere atual; //Caractere atual lido do codigo
    	atual = codigo.proxCaracter();
    	
    	if (atual != null){ //Hah mais caracteres a ler
    		if (Caractere.isletra(atual.getCaractere()))
                return identificador(atual.getCaractere(), atual.getLinha());
            else if (atual.getCaractere() == '-') 
                return primeiroMenos(atual.getLinha());
            else if (Caractere.isDigito(atual.getCaractere()))
                return numero(String.valueOf(atual.getCaractere()), atual.getLinha());
            else if (atual.getCaractere() == '>' || atual.getCaractere() == '<')
                return opRelacional(atual.getCaractere(), atual.getLinha());
            else if (atual.getCaractere() == '+')
                return primeiroMais(atual.getLinha());
            else if (atual.getCaractere() == '*')
                return new LinkToken("operador_multiplicacao", "*", atual.getLinha());
            else if (atual.getCaractere() == '!')
                return diferente(atual.getLinha());
            else if (atual.getCaractere() == '&')
            	return primeiroComercial(atual.getLinha());
            else if (atual.getCaractere() == '|')
            	return opOuLogico(atual.getLinha());
            else if (atual.getCaractere() == '=')
                return primeiroIgual(atual.getLinha());
            else if (atual.getCaractere() == '/'){
            	LinkToken temp = primeiroBarra(atual.getLinha());
            	if(temp.getToken().equals("comentario"))
            		return proximoToken();
            	else
            		return temp;
            }else if (atual.getCaractere() == ']' || atual.getCaractere() == '[' ||
            		atual.getCaractere() == '(' || atual.getCaractere() == ')' ||
            		atual.getCaractere() == '{' || atual.getCaractere() == '}' ||
            		atual.getCaractere() == ';' || atual.getCaractere() == ',' ||
            		atual.getCaractere() == '.')
                return new LinkToken("delimitador", String.valueOf(atual.getCaractere()),atual.getLinha());
            else if (atual.getCaractere() == '\n'||atual.getCaractere() == '\t' ||
            		atual.getCaractere() == ' '){
                eliminaEspacos();
                return proximoToken();
            }
            else if (atual.getCaractere() == '\'')
                return charconst(atual.getLinha());
            else{
            	ErroLexicoException erro = new ErroLexicoException();
                erro.addErros(String.valueOf(atual.getCaractere()), "Nao possivel iniciar token para este caractere", atual.getLinha());
                throw erro;
            }
    	}else //N�o h� mais caracteres a ler do c�digo
    		return null;
    }
    
    /**
     * Reconhece identificadores
     * @param caractereinicial - Primeiro caracter do identificador
     * @param linha - Linha do identificador
     * @return Token identificador
     */
    private LinkToken identificador(char caractereinicial, int linha){
    	String lexema = String.valueOf(caractereinicial); //Carregando caractere inicial no lexema
    	Caractere atual; //Caractere atual lido no codigo
    	byte estado = 0; //Estado do automato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 1;
    				else{
    					if (Caractere.isletra(atual.getCaractere()) || Caractere.isDigito(atual.getCaractere()) ||
    							atual.getCaractere() == '_') {
    	                    estado = 0;
    	                    lexema += atual.getCaractere();
    	                } else{
    	                	estado = 1;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: LinkToken corrente = new LinkToken("identificador", lexema, linha);
    				return corrente;
			default: estado = 0;
			}
		}
	}
    
    /**
     * Reconhece tokens iniciados por sinal "-"
     * @param linha - Linha do token
     * @return Token encontrado
     * @throws ErroLexicoException 
     */
    private LinkToken primeiroMenos(int linha) throws ErroLexicoException{
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 3;
    				else{
    					if (atual.getCaractere() == '-')
    						estado = 1;
    					else if (atual.getCaractere() == '>')
    						estado = 2;
    					else if (Caractere.isDigito(atual.getCaractere()))
    						return numero("-"+atual.getCaractere(), atual.getLinha());
    					else{
    						estado = 3;
    						codigo.retracao();
    					}
    				}
    				break;
    		case 1: return new LinkToken("incremento", "--", linha);
    		case 2: return new LinkToken("->", "->", linha);
    		case 3: return new LinkToken("operador_adicao", "-", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece n�meros
     * @param lexema - Lexema inicial do n�mero
     * @param linha - Linha do n�mero
     * @return Token encontrado
     * @throws ErroLexicoException 
     */
    private LinkToken numero(String lexema, int linha) throws ErroLexicoException{
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 4;
    				else{
	    				if (Caractere.isDigito(atual.getCaractere())){
			                estado = 0;
			                lexema += atual.getCaractere();
	    				}else if (atual.getCaractere() == '.'){
			                estado = 1;
			                lexema += atual.getCaractere();
	                	}else if (Caractere.isletra(atual.getCaractere())){
	                		lexema += atual.getCaractere();
			                ErroLexicoException erro = new ErroLexicoException();
			                erro.addErros(lexema, "Numero seguido de caractere", linha);
			                throw erro;
			            }else{
			            	estado = 4;
	    					codigo.retracao();
			            }
    				}
    				break;

    		case 1: atual = codigo.proxCaracter();
		    		if(atual == null){
		    			ErroLexicoException erro = new ErroLexicoException();
		                erro.addErros(lexema, "Ponto nao seguido de numero em numero flutuante", linha);
		                throw erro;
		    		}
					else{
						if (Caractere.isDigito(atual.getCaractere())){
							estado = 2;
							lexema += atual.getCaractere();
						}
						else{
							lexema += formataEspaco(atual.getCaractere());
							ErroLexicoException erro = new ErroLexicoException();
			                erro.addErros(lexema, "Ponto nao seguido de numero em numero flutuante", linha);
			                throw erro;
						}
					}
		    		break;
		    		
    		case 2: atual = codigo.proxCaracter();
    				if (atual == null)
    					estado = 3;
    				else{
    					if (Caractere.isDigito(atual.getCaractere())){
			                estado = 2;
			                lexema += atual.getCaractere();
	    				}else if (Caractere.isletra(atual.getCaractere())){
	                		lexema += atual.getCaractere();
			                ErroLexicoException erro = new ErroLexicoException();
			                erro.addErros(lexema, "Numero seguido de caractere", linha);
			                throw erro;
			            }else{
			            	estado = 3;
	    					codigo.retracao();
			            }
    				}
    				break;
    				
    		case 3: return new LinkToken("numero_flutuante", lexema, linha);
    		case 4: return new LinkToken("numero_inteiro", lexema, linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece operadores relacionais ( > , < , <=, >= )
     * @param caractereinicial - Caractere inicial do operador
     * @param linha - Linha do operador
     * @return Token operador relacional
     */
    private LinkToken opRelacional(char caractereinicial, int linha){
    	String lexema = String.valueOf(caractereinicial); //Carregando caractere inicial no lexema
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 2;
    				else{
    					if (atual.getCaractere() == '=') {
    	                    estado = 1;
    	                    lexema += atual.getCaractere();
    	                } else{
    	                	estado = 2;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: return new LinkToken("operador_relacional", lexema, linha);
    		case 2: return new LinkToken("operador_relacional", lexema, linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece operadorres iniciados com "+"
     * @param linha - Linha do operador
     * @return Token encontrado
     */
    private LinkToken primeiroMais(int linha){
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 2;
    				else{
    					if (atual.getCaractere() == '+')
    	                    estado = 1;
    	                else{
    	                	estado = 2;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: return new LinkToken("incremento", "++", linha);
    		case 2: return new LinkToken("operador_adicao", "+", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece operadorres relacional diferente
     * @param linha - Linha do operador
     * @return Token diferente
     * @throws ErroLexicoException 
     */
    private LinkToken diferente(int linha) throws ErroLexicoException{
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null){
    					ErroLexicoException erro = new ErroLexicoException();
		                erro.addErros("!", "! deve ser seguido de =", linha);
		                throw erro;
    				}else{
    					if (atual.getCaractere() == '=')
    	                    estado = 1;
    	                else{
    	                	ErroLexicoException erro = new ErroLexicoException();
    		                erro.addErros("!"+formataEspaco(atual.getCaractere()), "! deve ser seguido de =",
    		                		linha);
    		                throw erro;
    	                }
    				}
    				break;
    		case 1: return new LinkToken("operador_igualdade", "!=", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece operadorres iniciados com e-comercial
     * @param linha - Linha do operador
     * @return Token encontrado
     */
    private LinkToken primeiroComercial(int linha){
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 2;
    				else{
    					if (atual.getCaractere() == '&')
    	                    estado = 1;
    	                else{
    	                	estado = 2;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: return new LinkToken("operador_logico", "&&", linha);
    		case 2: return new LinkToken("&", "&", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece operador ou-logico
     * @param linha - Linha do operador
     * @return Token ou-logico
     * @throws ErroLexicoException 
     */
    private LinkToken opOuLogico(int linha) throws ErroLexicoException{
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null){
    					ErroLexicoException erro = new ErroLexicoException();
		                erro.addErros("|", "| deve ser seguido de outro |", linha);
		                throw erro;
    				}else{
    					if (atual.getCaractere() == '|')
    	                    estado = 1;
    	                else{
    	                	ErroLexicoException erro = new ErroLexicoException();
    		                erro.addErros("|"+formataEspaco(atual.getCaractere()), "| deve ser seguido de outro |",
    		                		linha);
    		                throw erro;
    	                }
    				}
    				break;
    		case 1: return new LinkToken("operador_logico", "||", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece tokens iniciados com "="
     * @param linha - Linha do operador
     * @return Token encontrado
     */
    private LinkToken primeiroIgual(int linha){
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 2;
    				else{
    					if (atual.getCaractere() == '=')
    	                    estado = 1;
    	                else{
    	                	estado = 2;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: return new LinkToken("operador_igualdade", "==", linha);
    		case 2: return new LinkToken("=", "=", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Reconhece tokens iniciados com "/"
     * @param linha - Linha do operador
     * @return Token encontrado
     * @throws ErroLexicoException 
     */
    private LinkToken primeiroBarra(int linha) throws ErroLexicoException{
    	String lexema = String.valueOf("/"); //Lexema do token
    	Caractere atual; //Caractere atual lido no c�digo
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null)
    					estado = 6;
    				else{
    					if (atual.getCaractere() == '*'){
    	                    estado = 1;
    	                    lexema += atual.getCaractere();
    					}else if (atual.getCaractere() == '/'){
    	                	estado = 4;
    	                	lexema += atual.getCaractere();
    	                }else{
    	                	estado = 6;
    	                	codigo.retracao();
    	                }
    				}
    				break;
    		case 1: atual = codigo.proxCaracter();
					if(atual == null){
						ErroLexicoException erro = new ErroLexicoException();
		                erro.addErros(lexema, "Comentario nao finalizado", linha);
		                throw erro;
					}else{
						if (atual.getCaractere() == '*'){
		                    estado = 2;
		                    lexema += atual.getCaractere();
						}else{
		                	estado = 1;
		                	lexema += atual.getCaractere();
		                }
					}
					break;
    		case 2: atual = codigo.proxCaracter();
					if(atual == null){
						ErroLexicoException erro = new ErroLexicoException();
		                erro.addErros(lexema, "Comentario nao finalizado", linha);
		                throw erro;
					}else{
						if (atual.getCaractere() == '*'){
		                    estado = 2;
		                    lexema += atual.getCaractere();
						}else if(atual.getCaractere() == '/'){
		                	estado = 3;
		                	lexema += atual.getCaractere();
		                }else{
		                	estado = 1;
		                	lexema += atual.getCaractere();
		                }
					}
					break;
    		case 3: return new LinkToken("comentario", lexema, linha);
    		case 4: atual = codigo.proxCaracter();
					if(atual == null){
						estado = 5;
					}else{
						if (atual.getCaractere() == '\n'){
		                    estado = 5;
						}else{
		                	estado = 4;
		                	lexema += atual.getCaractere();
		                }
					}
					break;
    		case 5: return new LinkToken("comentario", lexema, linha);
    		case 6: return new LinkToken("operador_multiplicacao", "/", linha);
			default: estado = 0;
			}
		}
    }
    
    /**
     * Elimina espacos, tabulacoes e quebras de linhas
     */
    private void eliminaEspacos(){
    	Caractere atual; //Caractere atual lido no c�digo
    	while(true){
    		atual = codigo.proxCaracter();
    		if(atual == null)
				return;
			else{
				if (atual.getCaractere() != ' ' && atual.getCaractere() != '\t' &&
					atual.getCaractere() != '\n'){
                	codigo.retracao();
					return;
                }
			}
		}
    }
    
    /**
     * Reconhece strings
     * @param linhainicial - Linha inical da string
     * @return Token string
     * @throws ErroLexicoException 
     */
    private LinkToken charconst(int linhainicial) throws ErroLexicoException{
    	String lexema = String.valueOf('\''); //Lexema do token
    	Caractere atual; //Caractere atual lido no c�digo
    	ErroLexicoException excecao = new ErroLexicoException();
    	byte estado = 0; //Estado do aut�mato
    	while(true){
    		switch(estado){
    		case 0: atual = codigo.proxCaracter();
    				if(atual == null){
		                excecao.addErros(lexema, "charconst nao finalizada", linhainicial);
		                throw excecao;
    				}else{
    					if (Character.isDigit(atual.getCaractere())||Character.isLetter(atual.getCaractere())
    							||(atual.getCaractere() >= 32 && atual.getCaractere() <=126 &&
    									atual.getCaractere() != 39)){
    						estado = 0;
    						lexema += atual.getCaractere();
    					}else if(atual.getCaractere() == '\''){
    						estado = 1;
    						lexema += atual.getCaractere();
    					}else
    	                	excecao.addErros(formataEspaco(atual.getCaractere()),
    	                			"Caracter nao permitido em charconst", atual.getLinha());
    				}
    				break;
    		case 1: if (excecao.size() == 0)
    					return new LinkToken("charconst", lexema, linhainicial);
    				else
    					throw excecao;
			default: estado = 0;
			}
		}
    }
    
    /**
     * Formata caracteres de espaco para que possam ser mostrados na interface do compilador
     * @param caractere - Caractere a ser formatado
     * @return String com o caractere formatado
     */
    private String formataEspaco(char caractere){
    	if (caractere == '\n')
    		return " \\n";
		else if (caractere == '\t')
			return " \\t";
		else if (caractere == ' ')
			return " ' '";
		else
			return String.valueOf(caractere);
    }
}